 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.resources;

 import java.net.URI ;
 import org.eclipse.core.filesystem.*;
 import org.eclipse.core.internal.localstore.FileSystemResourceManager;
 import org.eclipse.core.internal.properties.IPropertyManager;
 import org.eclipse.core.internal.utils.Messages;
 import org.eclipse.core.internal.utils.Policy;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.resources.team.IResourceTree;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.jobs.ILock;
 import org.eclipse.osgi.util.NLS;

 /**
  * @since 2.0
  *
  * Implementation note: Since the move/delete hook involves running third
  * party code, the workspace lock is not held. This means the workspace
  * lock must be re-acquired whenever we need to manipulate the workspace
  * in any way. All entry points from third party code back into the tree must
  * be done in an acquire/release pair.
  */
 class ResourceTree implements IResourceTree {

     private boolean isValid = true;
     private final FileSystemResourceManager localManager;
     /**
      * The lock to acquire when the workspace needs to be manipulated
      */
     private ILock lock;
     private MultiStatus multistatus;
     private int updateFlags;

     /**
      * Constructor for this class.
      */
     public ResourceTree(FileSystemResourceManager localManager, ILock lock, MultiStatus status, int updateFlags) {
         super();
         this.localManager = localManager;
         this.lock = lock;
         this.multistatus = status;
         this.updateFlags = updateFlags;
     }

     /**
      * @see IResourceTree#addToLocalHistory(IFile)
      */
     public void addToLocalHistory(IFile file) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             if (!file.exists())
                 return;
             IFileStore store = localManager.getStore(file);
             final IFileInfo fileInfo = store.fetchInfo();
             if (!fileInfo.exists())
                 return;
             localManager.getHistoryStore().addState(file.getFullPath(), store, fileInfo, false);
         } finally {
             lock.release();
         }
     }

     private IFileStore computeDestinationStore(IProjectDescription destDescription) throws CoreException {
         URI destLocation = destDescription.getLocationURI();
         // Use the default area if necessary for the destination.
 if (destLocation == null) {
             IPath rootLocation = ResourcesPlugin.getWorkspace().getRoot().getLocation();
             destLocation = rootLocation.append(destDescription.getName()).toFile().toURI();
         }
         return EFS.getStore(destLocation);
     }

     /**
      * @see IResourceTree#computeTimestamp(IFile)
      */
     public long computeTimestamp(IFile file) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             if (!file.getProject().exists())
                 return NULL_TIMESTAMP;
             return internalComputeTimestamp(file);
         } finally {
             lock.release();
         }
     }

     /**
      * Copies the local history of source to destination. Note that if source
      * is an IFolder, it is assumed that the same structure exists under destination
      * and the local history of any IFile under source will be copied to the
      * associated IFile under destination.
      */
     private void copyLocalHistory(IResource source, IResource destination) {
         localManager.getHistoryStore().copyHistory(source, destination, true);
     }

     /**
      * @see IResourceTree#deletedFile(IFile)
      */
     public void deletedFile(IFile file) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             // Do nothing if the resource doesn't exist.
 if (!file.exists())
                 return;
             try {
                 // Delete properties, generate marker deltas, and remove the node from the workspace tree.
 ((Resource) file).deleteResource(true, null);
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorDeleting, file.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, file.getFullPath(), message, e);
                 failed(status);
             }
         } finally {
             lock.release();
         }
     }

     /**
      * @see IResourceTree#deletedFolder(IFolder)
      */
     public void deletedFolder(IFolder folder) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             // Do nothing if the resource doesn't exist.
 if (!folder.exists())
                 return;
             try {
                 // Delete properties, generate marker deltas, and remove the node from the workspace tree.
 ((Resource) folder).deleteResource(true, null);
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorDeleting, folder.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, folder.getFullPath(), message, e);
                 failed(status);
             }
         } finally {
             lock.release();
         }
     }

     /**
      * @see IResourceTree#deletedProject(IProject)
      */
     public void deletedProject(IProject target) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             // Do nothing if the resource doesn't exist.
 if (!target.exists())
                 return;
             // Delete properties, generate marker deltas, and remove the node from the workspace tree.
 try {
                 ((Project) target).deleteResource(false, null);
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorDeleting, target.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, target.getFullPath(), message, e);
                 // log the status but don't return until we try and delete the rest of the project info
 failed(status);
             }
         } finally {
             lock.release();
         }
     }

     /**
      * Makes sure that the destination directory for a project move is unoccupied.
      * Returns true if successful, and false if the move should be aborted
      */
     private boolean ensureDestinationEmpty(IProject source, IFileStore destinationStore, IProgressMonitor monitor) throws CoreException {
         String message;
         //Make sure the destination location is unoccupied
 if (!destinationStore.fetchInfo().exists())
             return true;
         //check for existing children
 if (destinationStore.childNames(EFS.NONE, Policy.subMonitorFor(monitor, 0)).length > 0) {
             //allow case rename to proceed
 if (((Resource) source).getStore().equals(destinationStore))
                 return true;
             //fail because the destination is occupied
 message = NLS.bind(Messages.localstore_resourceExists, destinationStore);
             IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, null);
             failed(status);
             return false;
         }
         //delete the destination directory to allow for efficient renaming
 destinationStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, 0));
         return true;
     }

     /**
      * This operation has failed for the given reason. Add it to this
      * resource tree's status.
      */
     public void failed(IStatus reason) {
         Assert.isLegal(isValid);
         multistatus.add(reason);
     }

     /**
      * Returns the status object held onto by this resource tree.
      */
     protected IStatus getStatus() {
         return multistatus;
     }

     /**
      * @see IResourceTree#getTimestamp(IFile)
      */
     public long getTimestamp(IFile file) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             if (!file.exists())
                 return NULL_TIMESTAMP;
             ResourceInfo info = ((File) file).getResourceInfo(false, false);
             return info == null ? NULL_TIMESTAMP : info.getLocalSyncInfo();
         } finally {
             lock.release();
         }
     }

     /**
      * Returns the local timestamp for a file.
      *
      * @param file
      * @return The local file system timestamp
      */
     private long internalComputeTimestamp(IFile file) {
         IFileInfo fileInfo = localManager.getStore(file).fetchInfo();
         return fileInfo.exists() ? fileInfo.getLastModified() : NULL_TIMESTAMP;
     }

     /**
      * Helper method for #standardDeleteFile. Returns a boolean indicating whether or
      * not the delete was successful.
      */
     private boolean internalDeleteFile(IFile file, int flags, IProgressMonitor monitor) {
         try {
             String message = NLS.bind(Messages.resources_deleting, file.getFullPath());
             monitor.beginTask(message, Policy.totalWork);
             Policy.checkCanceled(monitor);

             // Do nothing if the file doesn't exist in the workspace.
 if (!file.exists()) {
                 // Indicate that the delete was successful.
 return true;
             }
             // Don't delete contents if this is a linked resource
 if (file.isLinked()) {
                 deletedFile(file);
                 return true;
             }
             // If the file doesn't exist on disk then signal to the workspace to delete the
 // file and return.
 IFileStore fileStore = localManager.getStore(file);
             boolean localExists = fileStore.fetchInfo().exists();
             if (!localExists) {
                 deletedFile(file);
                 // Indicate that the delete was successful.
 return true;
             }

             boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0;
             boolean force = (flags & IResource.FORCE) != 0;

             // Add the file to the local history if requested by the user.
 if (keepHistory)
                 addToLocalHistory(file);
             monitor.worked(Policy.totalWork / 4);

             // We want to fail if force is false and the file is not synchronized with the
 // local file system.
 if (!force) {
                 boolean inSync = isSynchronized(file, IResource.DEPTH_ZERO);
                 // only want to fail if the file still exists.
 if (!inSync && localExists) {
                     message = NLS.bind(Messages.localstore_resourceIsOutOfSync, file.getFullPath());
                     IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, file.getFullPath(), message);
                     failed(status);
                     // Indicate that the delete was unsuccessful.
 return false;
                 }
             }
             monitor.worked(Policy.totalWork / 4);

             // Try to delete the file from the file system.
 try {
                 fileStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork / 4));
                 // If the file was successfully deleted from the file system the
 // workspace tree should be updated accordingly.
 deletedFile(file);
                 // Indicate that the delete was successful.
 return true;
             } catch (CoreException e) {
                 message = NLS.bind(Messages.resources_couldnotDelete, fileStore.toString());
                 IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message, e);
                 failed(status);
             }
             // Indicate that the delete was unsuccessful.
 return false;
         } finally {
             monitor.done();
         }
     }

     /**
      * Helper method for #standardDeleteFolder. Returns a boolean indicating
      * whether or not the deletion of this folder was successful. Does a best effort
      * delete of this resource and its children.
      */
     private boolean internalDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) {
         String message = NLS.bind(Messages.resources_deleting, folder.getFullPath());
         monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$
 monitor.subTask(message);
         Policy.checkCanceled(monitor);

         // Do nothing if the folder doesn't exist in the workspace.
 if (!folder.exists())
             return true;

         // Don't delete contents if this is a linked resource
 if (folder.isLinked()) {
             deletedFolder(folder);
             return true;
         }

         // If the folder doesn't exist on disk then update the tree and return.
 IFileStore fileStore = localManager.getStore(folder);
         if (!fileStore.fetchInfo().exists()) {
             deletedFolder(folder);
             return true;
         }

         try {
             //this will delete local and workspace
 localManager.delete(folder, flags, Policy.subMonitorFor(monitor, Policy.totalWork));
         } catch (CoreException ce) {
             message = NLS.bind(Messages.localstore_couldnotDelete, folder.getFullPath());
             MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_DELETE_LOCAL, message, ce);
             if (ce.getStatus() != null)
                 status.merge(ce.getStatus());
             failed(status);
             return false;
         }
         return true;
     }

     /**
      * Does a best-effort delete on this resource and all its children.
      */
     private boolean internalDeleteProject(IProject project, int flags, IProgressMonitor monitor) {

         // Recursively delete each member of the project.
 IResource[] members = null;
         try {
             members = project.members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS);
         } catch (CoreException e) {
             String message = NLS.bind(Messages.resources_errorMembers, project.getFullPath());
             IStatus status = new ResourceStatus(IStatus.ERROR, project.getFullPath(), message, e);
             failed(status);
             // Indicate that the delete was unsuccessful.
 return false;
         }
         boolean deletedChildren = true;
         for (int i = 0; i < members.length; i++) {
             IResource child = members[i];
             switch (child.getType()) {
                 case IResource.FILE :
                     // ignore the .project file for now and delete it last
 if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(child.getName()))
                         deletedChildren &= internalDeleteFile((IFile) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length));
                     break;
                 case IResource.FOLDER :
                     deletedChildren &= internalDeleteFolder((IFolder) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length));
                     break;
             }
         }
         IFileStore projectStore = localManager.getStore(project);
         // Check to see if the children were deleted ok. If there was a problem
 // just return as the problem should have been logged by the recursive
 // call to the child.
 if (!deletedChildren)
             // Indicate that the delete was unsuccessful.
 return false;

         //Check if there are any undiscovered children of the project on disk other than description file
 String [] children;
         try {
             children = projectStore.childNames(EFS.NONE, null);
         } catch (CoreException e) {
             //treat failure to access the directory as a non-existent directory
 children = new String [0];
         }
         if (children.length != 1 || !IProjectDescription.DESCRIPTION_FILE_NAME.equals(children[0])) {
             String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, project.getName());
             failed(new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, project.getFullPath(), message));
             return false;
         }

         //Now delete the project description file
 IResource file = project.findMember(IProjectDescription.DESCRIPTION_FILE_NAME);
         if (file == null) {
             //the .project have may have been recreated on disk automatically by snapshot
 IFileStore dotProject = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME);
             try {
                 dotProject.delete(EFS.NONE, null);
             } catch (CoreException e) {
                 failed(e.getStatus());
             }
         } else {
             boolean deletedProjectFile = internalDeleteFile((IFile) file, flags, Policy.monitorFor(null));
             if (!deletedProjectFile) {
                 String message = NLS.bind(Messages.resources_couldnotDelete, file.getFullPath());
                 IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message);
                 failed(status);
                 // Indicate that the delete was unsuccessful.
 return false;
             }
         }

         //children are deleted, so now delete the parent
 try {
             projectStore.delete(EFS.NONE, null);
             deletedProject(project);
             // Indicate that the delete was successful.
 return true;
         } catch (CoreException e) {
             String message = NLS.bind(Messages.resources_couldnotDelete, projectStore.toString());
             IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, e);
             failed(status);
             // Indicate that the delete was unsuccessful.
 return false;
         }
     }

     /**
      * Return <code>true</code> if there is a change in the content area for the project.
      */
     private boolean isContentChange(IProject project, IProjectDescription destDescription) {
         IProjectDescription srcDescription = ((Project) project).internalGetDescription();
         URI srcLocation = srcDescription.getLocationURI();
         URI destLocation = destDescription.getLocationURI();
         if (srcLocation == null || destLocation == null)
             return true;
         //don't use URIUtil because we want to treat case rename as a content change
 return !srcLocation.equals(destLocation);
     }

     /**
      * Return <code>true</code> if there is a change in the name of the project.
      */
     private boolean isNameChange(IProject project, IProjectDescription description) {
         return !project.getName().equals(description.getName());
     }

     /**
      * @see IResourceTree#isSynchronized(IResource, int)
      */
     public boolean isSynchronized(IResource resource, int depth) {
         try {
             lock.acquire();
             return localManager.isSynchronized(resource, depth);
         } finally {
             lock.release();
         }
     }

     /**
      * The specific operation for which this tree was created has completed and this tree
      * should not be used anymore. Ensure that this is the case by making it invalid. This
      * is checked by all API methods.
      */
     void makeInvalid() {
         this.isValid = false;
     }

     /**
      * @see IResourceTree#movedFile(IFile, IFile)
      */
     public void movedFile(IFile source, IFile destination) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             // Do nothing if the resource doesn't exist.
 if (!source.exists())
                 return;
             // If the destination already exists then we have a problem.
 if (destination.exists()) {
                 String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message);
                 // log the status but don't return until we try and move the rest of the resource information.
 failed(status);
             }

             // Move the resource's persistent properties.
 IPropertyManager propertyManager = ((Resource) source).getPropertyManager();
             try {
                 propertyManager.copy(source, destination, IResource.DEPTH_ZERO);
                 propertyManager.deleteProperties(source, IResource.DEPTH_ZERO);
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                 // log the status but don't return until we try and move the rest of the resource information.
 failed(status);
             }

             // Move the node in the workspace tree.
 Workspace workspace = (Workspace) source.getWorkspace();
             try {
                 workspace.move((Resource) source, destination.getFullPath(), IResource.DEPTH_ZERO, updateFlags, false);
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                 // log the status but don't return until we try and move the rest of the resource information.
 failed(status);
             }

             // Generate the marker deltas.
 try {
                 workspace.getMarkerManager().moved(source, destination, IResource.DEPTH_ZERO);
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                 failed(status);
             }

             // Copy the local history information
 copyLocalHistory(source, destination);
         } finally {
             lock.release();
         }
     }

     /**
      * @see IResourceTree#movedFolderSubtree(IFolder, IFolder)
      */
     public void movedFolderSubtree(IFolder source, IFolder destination) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             // Do nothing if the source resource doesn't exist.
 if (!source.exists())
                 return;
             // If the destination already exists then we have an error.
 if (destination.exists()) {
                 String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message);
                 failed(status);
                 return;
             }

             // Move the folder properties.
 int depth = IResource.DEPTH_INFINITE;
             IPropertyManager propertyManager = ((Resource) source).getPropertyManager();
             try {
                 propertyManager.copy(source, destination, depth);
                 propertyManager.deleteProperties(source, depth);
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                 // log the status but don't return until we try and move the rest of the resource info
 failed(status);
             }

             // Create the destination node in the tree.
 Workspace workspace = (Workspace) source.getWorkspace();
             try {
                 workspace.move((Resource) source, destination.getFullPath(), depth, updateFlags, false);
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                 // log the status but don't return until we try and move the rest of the resource info
 failed(status);
             }

             // Generate the marker deltas.
 try {
                 workspace.getMarkerManager().moved(source, destination, depth);
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                 failed(status);
             }

             // Copy the local history for this folder
 copyLocalHistory(source, destination);
         } finally {
             lock.release();
         }
     }

     /**
      * @see IResourceTree#movedProjectSubtree(IProject, IProjectDescription)
      */
     public boolean movedProjectSubtree(IProject project, IProjectDescription destDescription) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             // Do nothing if the source resource doesn't exist.
 if (!project.exists())
                 return true;

             Project source = (Project) project;
             Project destination = (Project) source.getWorkspace().getRoot().getProject(destDescription.getName());
             Workspace workspace = (Workspace) source.getWorkspace();
             int depth = IResource.DEPTH_INFINITE;

             // If the name of the source and destination projects are not the same then
 // rename the meta area and make changes in the tree.
 if (isNameChange(source, destDescription)) {
                 if (destination.exists()) {
                     String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath());
                     IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message);
                     failed(status);
                     return false;
                 }

                 // Rename the project metadata area. Close the property store to flush everything to disk
 try {
                     source.getPropertyManager().closePropertyStore(source);
                     localManager.getHistoryStore().closeHistoryStore(source);
                 } catch (CoreException e) {
                     String message = NLS.bind(Messages.properties_couldNotClose, source.getFullPath());
                     IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                     // log the status but don't return until we try and move the rest of the resource info
 failed(status);
                 }
                 final IFileSystem fileSystem = EFS.getLocalFileSystem();
                 IFileStore oldMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(source));
                 IFileStore newMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(destination));
                 try {
                     oldMetaArea.move(newMetaArea, EFS.NONE, new NullProgressMonitor());
                 } catch (CoreException e) {
                     String message = NLS.bind(Messages.resources_moveMeta, oldMetaArea, newMetaArea);
                     IStatus status = new ResourceStatus(IResourceStatus.FAILED_WRITE_METADATA, destination.getFullPath(), message, e);
                     // log the status but don't return until we try and move the rest of the resource info
 failed(status);
                 }

                 // Move the workspace tree.
 try {
                     workspace.move(source, destination.getFullPath(), depth, updateFlags, true);
                 } catch (CoreException e) {
                     String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath());
                     IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                     // log the status but don't return until we try and move the rest of the resource info
 failed(status);
                 }

                 // Clear stale state on the destination project.
 ((ProjectInfo) destination.getResourceInfo(false, true)).fixupAfterMove();

                 // Generate marker deltas.
 try {
                     workspace.getMarkerManager().moved(source, destination, depth);
                 } catch (CoreException e) {
                     String message = NLS.bind(Messages.resources_errorMarkersMove, source.getFullPath(), destination.getFullPath());
                     IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                     // log the status but don't return until we try and move the rest of the resource info
 failed(status);
                 }
                 // Copy the local history
 copyLocalHistory(source, destination);
             }

             // Write the new project description on the destination project.
 try {
                 //moving linked resources may have modified the description in memory
 ((ProjectDescription) destDescription).setLinkDescriptions(destination.internalGetDescription().getLinks());
                 destination.internalSetDescription(destDescription, true);
                 destination.writeDescription(IResource.FORCE);
             } catch (CoreException e) {
                 String message = Messages.resources_projectDesc;
                 IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e);
                 failed(status);
             }

             // write the private project description, including the project location
 try {
                 workspace.getMetaArea().writePrivateDescription(destination);
             } catch (CoreException e) {
                 failed(e.getStatus());
             }

             // Do a refresh on the destination project to pick up any newly discovered resources
 try {
                 destination.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
             } catch (CoreException e) {
                 String message = NLS.bind(Messages.resources_errorRefresh, destination.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e);
                 failed(status);
                 return false;
             }
             return true;
         } finally {
             lock.release();
         }
     }

     /**
      * Helper method for moving the project content. Determines the content location
      * based on the project description. (default location or user defined?)
      */
     private void moveProjectContent(IProject source, IFileStore destStore, int flags, IProgressMonitor monitor) throws CoreException {
         try {
             String message = NLS.bind(Messages.resources_moving, source.getFullPath());
             monitor.beginTask(message, 10);
             IProjectDescription srcDescription = source.getDescription();
             URI srcLocation = srcDescription.getLocationURI();
             // If the locations are the same (and non-default) then there is nothing to do.
 if (srcLocation != null && URIUtil.equals(srcLocation, destStore.toURI()))
                 return;

             //If this is a replace, just make sure the destination location exists, and return
 boolean replace = (flags & IResource.REPLACE) != 0;
             if (replace) {
                 destStore.mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 10));
                 return;
             }

             // Move the contents on disk.
 localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 9));

             //if this is a deep move, move the contents of any linked resources
 if ((flags & IResource.SHALLOW) == 0) {
                 IResource[] children = source.members();
                 for (int i = 0; i < children.length; i++) {
                     if (children[i].isLinked()) {
                         message = NLS.bind(Messages.resources_moving, children[i].getFullPath());
                         monitor.subTask(message);
                         IFileStore linkDestination = destStore.getChild(children[i].getName());
                         try {
                             localManager.move(children[i], linkDestination, flags, Policy.monitorFor(null));
                         } catch (CoreException ce) {
                             //log the failure, but keep trying on remaining links
 failed(ce.getStatus());
                         }
                     }
                 }
             }
             monitor.worked(1);
         } finally {
             monitor.done();
         }
     }

     /**
      * @see IResourceTree#standardDeleteFile(IFile, int, IProgressMonitor)
      */
     public void standardDeleteFile(IFile file, int flags, IProgressMonitor monitor) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             internalDeleteFile(file, flags, monitor);
         } finally {
             lock.release();
         }
     }

     /**
      * @see IResourceTree#standardDeleteFolder(IFolder, int, IProgressMonitor)
      */
     public void standardDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             internalDeleteFolder(folder, flags, monitor);
         } finally {
             lock.release();
             monitor.done();
         }
     }

     /**
      * @see IResourceTree#standardDeleteProject(IProject, int, IProgressMonitor)
      */
     public void standardDeleteProject(IProject project, int flags, IProgressMonitor monitor) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             String message = NLS.bind(Messages.resources_deleting, project.getFullPath());
             monitor.beginTask(message, Policy.totalWork);
             // Do nothing if the project doesn't exist in the workspace tree.
 if (!project.exists())
                 return;

             boolean alwaysDeleteContent = (flags & IResource.ALWAYS_DELETE_PROJECT_CONTENT) != 0;
             //force is implied if alwaysDeleteContent is true
 if (alwaysDeleteContent)
                 flags |= IResource.FORCE;
             boolean force = (flags & IResource.FORCE) != 0;
             boolean neverDeleteContent = (flags & IResource.NEVER_DELETE_PROJECT_CONTENT) != 0;
             boolean success = true;

             // Delete project content. Don't do anything if the user specified explicitly asked
 // not to delete the project content or if the project is closed and
 // ALWAYS_DELETE_PROJECT_CONTENT was not specified.
 if (alwaysDeleteContent || (project.isOpen() && !neverDeleteContent)) {
                 // Check to see if we are synchronized with the local file system. If we are in sync then
 // we can short circuit this operation and delete all the files on disk, otherwise we have
 // to recursively try and delete them doing best-effort, thus leaving only the ones which
 // were out of sync.
 if (!force && !isSynchronized(project, IResource.DEPTH_INFINITE)) {
                     // we are not in sync and force is false so delete via best effort
 success = internalDeleteProject(project, flags, monitor);
                     if (!success) {
                         IFileStore store = localManager.getStore(project);
                         message = NLS.bind(Messages.resources_couldnotDelete, store.toString());
                         IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message);
                         failed(status);
                     }
                     return;
                 }

                 try {
                     IFileStore projectStore = localManager.getStore(project);
                     // if the project is open, we must perform a best-effort deletion
 if (project.isOpen()) {
                         //use force because we already checked for synchronization above
 localManager.delete(project, flags & IResource.FORCE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8));
                     } else {
                         projectStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8));
                     }
                 } catch (CoreException ce) {
                     message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath());
                     MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_DELETE_LOCAL, message, ce);
                     if (ce.getStatus() != null)
                         status.merge(ce.getStatus());
                     failed(status);
                     return;
                 }
             }

             // Signal that the workspace tree should be updated that the project has been deleted.
 if (success)
                 deletedProject(project);
             else {
                 message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath());
                 IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message);
                 failed(status);
             }
         } finally {
             lock.release();
             monitor.done();
         }
     }

     /**
      * @see IResourceTree#standardMoveFile(IFile, IFile, int, IProgressMonitor)
      */
     public void standardMoveFile(IFile source, IFile destination, int flags, IProgressMonitor monitor) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             String message = NLS.bind(Messages.resources_moving, source.getFullPath());
             monitor.subTask(message);

             // These pre-conditions should all be ok but just in case...
 if (!source.exists() || destination.exists() || !destination.getParent().isAccessible())
                 throw new IllegalArgumentException ();

             boolean force = (flags & IResource.FORCE) != 0;
             boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0;
             boolean isDeep = (flags & IResource.SHALLOW) == 0;

             // If the file is not in sync with the local file system and force is false,
 // then signal that we have an error.
 if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) {
                 message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath());
                 IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message);
                 failed(status);
                 return;
             }
             monitor.worked(Policy.totalWork / 4);

             // Add the file contents to the local history if requested by the user.
 if (keepHistory)
                 addToLocalHistory(source);
             monitor.worked(Policy.totalWork / 4);

             //for shallow move of linked resources, nothing needs to be moved in the file system
 if (!isDeep && source.isLinked()) {
                 movedFile(source, destination);
                 return;
             }

             // If the file was successfully moved in the file system then the workspace
 // tree needs to be updated accordingly. Otherwise signal that we have an error.
 IFileStore destStore = null;
             boolean failedDeletingSource = false;
             try {
                 destStore = localManager.getStore(destination);
                 //ensure parent of destination exists
 destStore.getParent().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 0));
                 localManager.move(source, destStore, flags, monitor);
             } catch (CoreException e) {
                 failed(e.getStatus());
                 // did the fail occur after copying to the destination?
 failedDeletingSource = destStore != null && destStore.fetchInfo().exists();
                 // if so, we should proceed
 if (!failedDeletingSource)
                     return;
             }
             movedFile(source, destination);
             updateMovedFileTimestamp(destination, internalComputeTimestamp(destination));
             if (failedDeletingSource) {
                 //recreate source file to ensure we are not out of sync
 try {
                     source.refreshLocal(IResource.DEPTH_INFINITE, null);
                 } catch (CoreException e) {
                     //ignore secondary failure - we have already logged the main failure
 }
             }
             monitor.worked(Policy.totalWork / 4);
             return;
         } finally {
             lock.release();
             monitor.done();
         }
     }

     /**
      * @see IResourceTree#standardMoveFolder(IFolder, IFolder, int, IProgressMonitor)
      */
     public void standardMoveFolder(IFolder source, IFolder destination, int flags, IProgressMonitor monitor) {
         Assert.isLegal(isValid);
         try {
             lock.acquire();
             String message = NLS.bind(Messages.resources_moving, source.getFullPath());
             monitor.beginTask(message, 100);

             // These pre-conditions should all be ok but just in case...
 if (!source.exists() || destination.exists() || !destination.getParent().isAccessible())
                 throw new IllegalArgumentException ();

             // Check to see if we are synchronized with the local file system. If we are in sync then we can
 // short circuit this method and do a file system only move. Otherwise we have to recursively
 // try and move all resources, doing it in a best-effort manner.
 boolean force = (flags & IResource.FORCE) != 0;
             if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) {
                 message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath());
                 IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message);
                 failed(status);
                 return;
             }
             monitor.worked(20);

             //for linked resources, nothing needs to be moved in the file system
 boolean isDeep = (flags & IResource.SHALLOW) == 0;
             if (!isDeep && source.isLinked()) {
                 movedFolderSubtree(source, destination);
                 return;
             }

             // Move the resources in the file system. Only the FORCE flag is valid here so don't
 // have to worry about clearing the KEEP_HISTORY flag.
 IFileStore destStore = null;
             boolean failedDeletingSource = false;
             try {
                 destStore = localManager.getStore(destination);
                 localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 60));
             } catch (CoreException e) {
                 failed(e.getStatus());
                 // did the fail occur after copying to the destination?
 failedDeletingSource = destStore != null && destStore.fetchInfo().exists();
                 // if so, we should proceed
 if (!failedDeletingSource)
                     return;
             }
             movedFolderSubtree(source, destination);
             monitor.worked(20);
             updateTimestamps(destination, isDeep);
             if (failedDeletingSource) {
                 //the move could have been partially successful, so refresh to ensure we are in sync
 try {
                     source.refreshLocal(IResource.DEPTH_INFINITE, null);
                     destination.refreshLocal(IResource.DEPTH_INFINITE, null);
                 } catch (CoreException e) {
                     //ignore secondary failures -we have already logged main failure
 }
             }
         } finally {
             lock.release();
             monitor.done();
        }
    }

    /**
     * @see IResourceTree#standardMoveProject(IProject, IProjectDescription, int, IProgressMonitor)
     */
    public void standardMoveProject(IProject source, IProjectDescription description, int flags, IProgressMonitor monitor) {
        Assert.isLegal(isValid);
        try {
            lock.acquire();
            String message = NLS.bind(Messages.resources_moving, source.getFullPath());
            monitor.beginTask(message, Policy.totalWork);

            // Double-check this pre-condition.
 if (!source.isAccessible())
                throw new IllegalArgumentException ();

            // If there is nothing to do on disk then signal to make the workspace tree
 // changes.
 if (!isContentChange(source, description)) {
                movedProjectSubtree(source, description);
                return;
            }

            // Check to see if we are synchronized with the local file system.
 boolean force = (flags & IResource.FORCE) != 0;
            if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) {
                // FIXME: make this a best effort move?
 message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath());
                IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message);
                failed(status);
                return;
            }

            IFileStore destinationStore;
            try {
                destinationStore = computeDestinationStore(description);
                //destination can be non-empty on replace
 if ((flags & IResource.REPLACE) == 0)
                    if (!ensureDestinationEmpty(source, destinationStore, monitor))
                        return;
            } catch (CoreException e) {
                //must fail if the destination location cannot be accessd (undefined file system)
 message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath());
                IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                failed(status);
                return;
            }

            // Move the project content in the local file system.
 try {
                moveProjectContent(source, destinationStore, flags, Policy.subMonitorFor(monitor, Policy.totalWork * 3 / 4));
            } catch (CoreException e) {
                message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath());
                IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
                failed(status);
                //refresh the project because it might have been partially moved
 try {
                    source.refreshLocal(IResource.DEPTH_INFINITE, null);
                } catch (CoreException e2) {
                    //ignore secondary failures
 }
            }

            // If we got this far the project content has been moved on disk (if necessary)
 // and we need to update the workspace tree.
 movedProjectSubtree(source, description);
            monitor.worked(Policy.totalWork * 1 / 8);

            boolean isDeep = (flags & IResource.SHALLOW) == 0;
            updateTimestamps(source.getWorkspace().getRoot().getProject(description.getName()), isDeep);
            monitor.worked(Policy.totalWork * 1 / 8);
        } finally {
            lock.release();
            monitor.done();
        }
    }

    /**
     * @see IResourceTree#updateMovedFileTimestamp(IFile, long)
     */
    public void updateMovedFileTimestamp(IFile file, long timestamp) {
        Assert.isLegal(isValid);
        try {
            lock.acquire();
            // Do nothing if the file doesn't exist in the workspace tree.
 if (!file.exists())
                return;
            // Update the timestamp in the tree.
 ResourceInfo info = ((Resource) file).getResourceInfo(false, true);
            // The info should never be null since we just checked that the resource exists in the tree.
 localManager.updateLocalSync(info, timestamp);
            //remove the linked bit since this resource has been moved in the file system
 info.clear(ICoreConstants.M_LINK);
        } finally {
            lock.release();
        }
    }

    /**
     * Helper method to update all the timestamps in the tree to match
     * those in the file system. Used after a #move.
     */
    private void updateTimestamps(IResource root, final boolean isDeep) {
        IResourceVisitor visitor = new IResourceVisitor() {
            public boolean visit(IResource resource) {
                if (resource.isLinked()) {
                    if (isDeep) {
                        //clear the linked resource bit, if any
 ResourceInfo info = ((Resource) resource).getResourceInfo(false, true);
                        info.clear(ICoreConstants.M_LINK);
                    }
                    return true;
                }
                //only needed if underlying file system does not preserve timestamps
 // if (resource.getType() == IResource.FILE) {
 // IFile file = (IFile) resource;
 // updateMovedFileTimestamp(file, computeTimestamp(file));
 // }
 return true;
            }
        };
        try {
            root.accept(visitor, IResource.DEPTH_INFINITE, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS);
        } catch (CoreException e) {
            // No exception should be thrown.
 }
    }
}

